import { emptyNode, HTML_NODE_TYPE, HTML_TAG_STATUS } from '../../constant';
import HNode from '../../type/HNode';
import HtmlParser from './HtmlParser';
import { errorProcess, sliceMatchResult } from './helper';
import { ErrorMessage } from './ErrorMessage';

// 正常内容标签的正则表达式
const qname = '[a-zA-Z_][a-zA-Z0-9\\-\\.\\_]*';
const TAG_START = `^<(/)?(${qname})`;

const TAG_START_REGEX = new RegExp(TAG_START);
const TAG_FINISH_REGEX = /^\s*(\/)?>/

const TAG_DETECTOR = `</?(${qname})`;
const TAG_DETECTOR_REGEX = new RegExp(TAG_DETECTOR);

// 注释内容标签的正则表达式
const COMMENT_START_REGEX = /^<!--/;
const COMMENT_FINISH_REGEX = /-->/;

// 属性内容的正则表达式
const ATTR_REGEX = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;

/**
 * 创建节点 HNode 对象
 * @param {String} tag 
 * @param {HTML_TAG_STATUS} status 
 * @param {Array<{key:String, value:String}>} attrsList
 * @param {Number} start
 * @returns {HNode}
 */
export function createTagHNode (tag, status, attrsList,start) {
    const type = HTML_NODE_TYPE.TAG;
    return new HNode(
        tag,
        type,
        status,
        attrsList,
        start,
        undefined
    );
}

/**
 * 创建注释 HNode 对象
 * @param {String} value 注释值
 * @param {Number} start 改标签开始的字符数
 * @return {HNode}
 */
export function createCommentHNode (value, start) {
    const tag = 'comment';
    const type = HTML_NODE_TYPE.COMMENT;
    const status = HTML_TAG_STATUS.SINGLE;
    const attrsList = new Array();
    attrsList.push({ name: 'value', value: value });

    return new HNode(
        tag, 
        type, 
        status, 
        attrsList,
        start,
        undefined
    );
}

/**
 * 创建文本 HNode 对象
 * @param {String} value 文本值
 * @param {Number} start 改标签开始的字符数
 * @returns {HNode}
 */
export function createTextHNode (value, start) {
    const tag = 'text';
    const type = HTML_NODE_TYPE.TEXT;
    const status = HTML_TAG_STATUS.SINGLE;
    const attrsList = new Array();

    return new HNode(
        tag,
        type,
        status,
        attrsList,
        start,
        value
    );
}

/**
 * 分析标签，并将返回结果以以下方式返回
 * 1. 如果开头内容是文字
 * {
 *      NODE_TYPE: HTML_NODE_TYPE.TEXT,
 *      HTML_NODE: HNode(Text)
 * }
 * 2. 如果开头内容是注释
 * {
 *      NODE_TYPE: HTML_NODE_TYPE.COMMENT,
 *      HTML_NODE: HNode(COMMENT)
 * }
 * 3. 如果开头内容是标签
 * {
 *      NODE_TYPE: HTML_NODE_TYPE.TAG,
 *      HTML_NODE: HNode(TAG)
 * }
 * @param {HtmlParser} parser
 * @returns {HNode}
 */
export function parseNode (parser) {
    // 初始化 返回结果
    const Result = {
        NODE_TYPE: undefined,
        HTML_NODE: null,
    };

    // 处理无效字符
    clearWhiteSpaceAndLineBreak(parser);
    // 是否是标签
    if (TAG_START_REGEX.test(parser.HTML_STR)) {
        Result.NODE_TYPE = HTML_NODE_TYPE.TAG;
        Result.HTML_NODE = parseHtmlTag(parser);
    }
    // 是否是注释
    else if (COMMENT_START_REGEX.test(parser.HTML_STR)) {
        Result.NODE_TYPE = HTML_NODE_TYPE.COMMENT;
        Result.HTML_NODE = parseHtmlComment(parser);
    }
    // 则为文本
    else {
        Result.NODE_TYPE = HTML_NODE_TYPE.TEXT;
        Result.HTML_NODE = parseHtmlText(parser);
    }

    // 返回结果
    return Result;
}

/**
 * 清理字符串开头的 空格 和 换行符
 * @param {HtmlParser} parser HTML解析器实例
 */
function clearWhiteSpaceAndLineBreak (parser) {
    let count = 0;
    while (parser.HTML_STR[count] === ' '
        || parser.HTML_STR[count] === '\t'
        || parser.HTML_STR[count] === '\n'
        || parser.HTML_STR[count] === '\r') {
        // 到达字符串长度，退出
        if (count === parser.HTML_STR.length) {
            break;
        }
        // 如果遇到换行符 \r\n  加2
        if (parser.HTML_STR[count] === '\r' && parser.HTML_STR[count+1] === '\n') {
            count += 2;
        }
        // 其他的字符加 1
        else {
            count += 1;
        }
    }
    // 将 无效字符的数量 相加
    parser.charCount += count;
    // 将 无效字符串 切除
    parser.HTML_STR = parser.HTML_STR.substring(count);
}

/**
 * 分析HTML标签，并分析出HTML标签的
 * 标签名称，标签的状态(开口标签，闭口标签，独立标签), 标签的属性
 * @param {HtmlParser} parser
 */
function parseHtmlTag (parser) {
    const tagResult = parser.HTML_STR.match(TAG_START_REGEX);
    // 标签不是闭口标签
    if (!tagResult[1]) {
        // 保留该标签的开始的字符串数
        const start = parser.charCount;
        // ============ 获取标签名称 ============================
        const tag = tagResult[2];
        // 切除匹配结果
        parser.HTML_STR = sliceMatchResult(parser.HTML_STR, tagResult, parser);

        // ============ 获取标签属性 ============================
        const attrsList = new Array();
        while (ATTR_REGEX.test(parser.HTML_STR)) {
            // 属性匹配结果
            const attrPatternResult = parser.HTML_STR.match(ATTR_REGEX);
            // 切除匹配结果
            parser.HTML_STR = sliceMatchResult(parser.HTML_STR, attrPatternResult, parser);
            // 组装属性对象
            const attr = new Object();
            attr.key = attrPatternResult[1];
            if (attrPatternResult[3]) {
                attr.value = attrPatternResult[3];
            }
            // 用于维持 Attr 类型一致性
            else {
                attr.value = undefined;
            }
            // 推入属性数组
            attrsList.push(attr);
        };

        // ============ 获取标签状态 ============================
        const statusResult = parser.HTML_STR.match(TAG_FINISH_REGEX);
        // 切除匹配结果
        parser.HTML_STR = sliceMatchResult(parser.HTML_STR, statusResult, parser);
        // 判断标签状态
        if (!statusResult[1]) {
            // 返回 开口标签 HNode节点
            return createTagHNode(tag, HTML_TAG_STATUS.OPEN, attrsList, start);
        }
        else {
            // 返回 独立标签 HNode节点
            return createTagHNode(tag, HTML_TAG_STATUS.SINGLE, attrsList, start);
        }
    } 
    // 标签是闭口标签
    else {
        // 保留该标签的开始的字符串数
        const start = parser.charCount;
        // ============ 获取标签名称 ============================
        const tag = tagResult[2];
        // 切除匹配结果
        parser.HTML_STR = sliceMatchResult(parser.HTML_STR, tagResult, parser);
        // ============ 匹配标签的结束部分 ============================
        const statusResult = parser.HTML_STR.match(TAG_FINISH_REGEX);
        // 切除匹配结果
        parser.HTML_STR = sliceMatchResult(parser.HTML_STR, statusResult, parser);
        // 返回 闭口标签 HNode节点
        return createTagHNode(tag, HTML_TAG_STATUS.CLOSE, [], start);
    }
}

/**
 * 分析注释标签，将内容截取
 * @param {HtmlParser} parser
 */
function parseHtmlComment (parser) {
    // 保留注释节点的开始字符位置
    const start = parser.charCount;

    // 查找注释节点的开始位置和结束位置
    const startResult = parser.HTML_STR.match(COMMENT_START_REGEX);
    const startIndex = startResult.index;
    const finishResult = parser.HTML_STR.match(COMMENT_FINISH_REGEX);
    const finishIndex = finishResult.index + finishResult[0].length;

    // 获取注释节点的值
    const value = parser.HTML_STR.substring(startIndex, finishIndex);
    // 将注释节点的长度添加到 charCount
    parser.charCount += value.length;
    // 切割注释节点字符串
    parser.HTML_STR = parser.HTML_STR.substring(finishIndex);

    return createCommentHNode(value, start);
}

/**
 * 转换 换行符, 并去除 前缀空格 与 后缀空格
 * @param {String} value
 */
function transformNewLine (value) {
    let textContent = value;
    while (textContent.indexOf("\n") !== -1) {
        textContent = textContent.replace("\n", "");
    }

    textContent = textContent.trimLeft();
    textContent = textContent.trimRight();

    return textContent;
}

/**
 * 分析文本内容，将内容截取
 * @param {HtmlParser} parser
 */
function parseHtmlText (parser) {
    let value = undefined;
    // 保留文字节点的字符开始位置
    const start = parser.charCount;

    // 存在标签，注释等节点需要进一步分析
    if (TAG_DETECTOR_REGEX.test(parser.HTML_STR)) {
        const tagResult = parser.HTML_STR.match(TAG_DETECTOR_REGEX);
        const finishIndex = tagResult.index;

        // 获取文字节点的值
        value = parser.HTML_STR.substring(0, finishIndex);
        // 将文字字符串的长度与charCount 相加
        parser.charCount += value.length;
        // 切割文字字符串
        parser.HTML_STR = parser.HTML_STR.substring(finishIndex);
    } 
    // 不存在标签，注释等节点需要进一步分析
    else {
        // 获取文字节点的值
        value = new String(parser.HTML_STR);
        // 将文字字符串的长度与charCount 相加
        parser.charCount += value.length;
        // 切割文字字符串
        parser.HTML_STR = "";
    }

    // 对 文本内容 中的换行符，制表符 置换为 ""
    value = transformNewLine(value);
    if (value !== "") {
        return createTextHNode(value, start);
    } else {
        return emptyNode;
    }
}

// ================================== 处理特殊文本标签 ==============================
// script, style, textarea 标签

const PLAIN_TEXT_TAG_FINISH_REGEX = /^<\/(script|textarea|style)>/;

const ClOSE_TAG_REGEX = /^<\/?([a-zA-Z_][a-zA-Z0-9\\-\\.]*)/;

const STR_DETECTOR_REGEX = {
    0x22: /^"[^"]+?"/,
    0x27: /^'[^']+?'/,
    0x60: /^`[^`]+?`/
};

/**
 * 处理标签 script style 标签
 * @param {HNode} hNode
 * @param {HtmlParser} parser
 */
export function parseScriptOrStyle (hNode, parser) {
    // 保留特殊文本节点的开始字符位置
    const start = parser.charCount;
    // 检测变量初始化
    const htmlStr = parser.HTML_STR;
    let index = 0;
    
    // 开始处理文本字符串
    while (index < htmlStr.length) {
        // 获取当前字符的 char code
        const charCode = htmlStr.charCodeAt(index);
        // 检查是否为特殊字符
        switch (charCode) {
            // 检查字符串
            // 如果字符串没有配对成功，则报错
            case (0x22): 
            case (0x27): 
            case (0x60): {
                const process_str = htmlStr.substring(index);
                if (STR_DETECTOR_REGEX[charCode].test(process_str)) {
                    const Result = process_str.match(STR_DETECTOR_REGEX[charCode]);
                    const str_length = Result[0].length;
                    index += str_length;
                }
                else {
                    errorProcess(hNode, ErrorMessage.SyntaxError, htmlStr);
                }
                break;
            }
            // 检查结束标签
            // 如果结束标签不配对，则报错
            case (0x3c): {
                const pre_sql = htmlStr.substring(0, index);
                const process_sql = htmlStr.substring(index);
                // 检测是否为 Plain Text Tag 的结束标签
                if (TAG_START_REGEX.test(process_sql)) {
                    const Result = process_sql.match(TAG_START_REGEX);
                    const isCloseTag = Result[1] === '/' ? true : false;
                    const tagName = Result[2];
                    // 确定为匹配的结束标签
                    if (parser.PLAIN_TEXT_TAG === tagName && isCloseTag) {
                        // Html Node 节点内容修改
                        const textContent = pre_sql;
                        // 创建文本节点
                        const textHNode = createTextHNode(textContent, start);
                        // 设置父子关系
                        textHNode.parent = hNode;
                        hNode.children.push(textHNode);
                        // 修改 Plain Text 标签的状态
                        hNode.status = HTML_TAG_STATUS.SINGLE;

                        // ==============================
                        // 截断 HTML_STR 字符串
                        // 截断 Plain Text Tag 的文本值
                        parser.HTML_STR = parser.HTML_STR.substring(index);
                        // 字符计数器 与 文字内容 长度相加
                        parser.charCount += index;

                        // 截断 Plain Text Tag 的结束标签
                        const tagStartMatchResult = parser.HTML_STR.match(TAG_START_REGEX);
                        parser.HTML_STR = parser.HTML_STR.replace(TAG_START_REGEX, "");
                        const tagFinishMatchResult = parser.HTML_STR.match(TAG_FINISH_REGEX);
                        parser.HTML_STR = parser.HTML_STR.replace(TAG_FINISH_REGEX, "");
                        // 字符计数器 与 结束标签 长度相加
                        parser.charCount += tagStartMatchResult[0].length + tagFinishMatchResult[0].length;

                        return;
                    } 
                } 
                // Plain Text Tag 无配对标签
                errorProcess(hNode, ErrorMessage.NoMatchTag, htmlStr);
                break;
            }
        }

        // while 循环量 ++
        index ++;
    }
    
    // 值匹配到字符串，没有匹配到标签
    // Plain Text Tag 无配对标签
    errorProcess(hNode, ErrorMessage.NoMatchTag, htmlStr);
}

/**
 * 处理标签 textarea 标签
 * @param {HNode} hNode
 * @param {HtmlParser} parser
 */
export function parseTextarea (hNode, parser) {
    // 保留特殊文本节点的开始字符位置
    const start = parser.charCount;
    // 检测变量初始化
    const htmlStr = parser.HTML_STR;
    let index = 0;

    while (index < htmlStr.length) {
        const charCode = htmlStr.charCodeAt(index);
        if (charCode === 0x3C) {
            const pre_sql = htmlStr.substring(0, index);
            const process_sql = htmlStr.substring(index);
            const isCloseTag = ClOSE_TAG_REGEX.test(process_sql);
            const Result = process_sql.match(TAG_START_REGEX)
            const tagName = Result[2];
            if (isCloseTag && tagName == 'textarea') {
                // 获取文字节点内容
                const textContent = pre_sql;
                // 将文字内容长度  与 charCount 相加
                parser.charCount += textContent.length;
                // 创建文本节点
                const textHNode = createTextHNode(textContent, start);
                // 设置父子关系
                textHNode.parent = hNode;
                hNode.children.push(textHNode);
                // 修改 Plain Text 标签的状态
                hNode.status = HTML_TAG_STATUS.SINGLE;

                // ==============================
                // 截断 HTML_STR 字符串
                // 截断 Plain Text Tag 的文本值
                parser.HTML_STR = parser.HTML_STR.substring(index);
                // 截断 Plain Text Tag 的结束标签
                const tagStartMatchResult = parser.HTML_STR.match(TAG_START_REGEX);
                parser.HTML_STR = parser.HTML_STR.replace(TAG_START_REGEX, "");
                const tagFinishMatchResult = parser.HTML_STR.match(TAG_FINISH_REGEX);
                parser.HTML_STR = parser.HTML_STR.replace(TAG_FINISH_REGEX, "");
                // 字符计数器 与 结束标签 长度相加
                parser.charCount += tagStartMatchResult[0].length + tagFinishMatchResult[0].length;

                return;
            }
            else {
                errorProcess(hNode, ErrorMessage.NoMatchTag, htmlStr);
            }
        }
        index ++;
    }

    // 值匹配到字符串，没有匹配到标签
    // Plain Text Tag 无配对标签
    errorProcess(hNode, ErrorMessage.NoMatchTag, htmlStr);
}